#include "qbinaryrpc_server.h"
#include "qbinaryrpc_incomingconnection.h"
#include "qbinaryrpc_response.h"
#include "qbinaryrpc_request.h"

namespace qbinaryrpc {

class MethodSignature {
public:
  MethodSignature(QVariant::Type return_type,
                  const QList<QVariant::Type> &parameters_types)
    : return_type(return_type),
      parameters_types(parameters_types) {}
  bool checkParameterTypes(const QVariantList &parameters_types) {
    if(parameters_types.count() != this->parameters_types.count()) 
      return false;
    for (int i = 0; i < parameters_types.count(); i++)
      if (parameters_types[i].type() != this->parameters_types[i])
        return false;
    return true;
  }
  QVariant::Type return_type;
  QList<QVariant::Type> parameters_types;
};

} //namespace qbinaryrpc

using namespace qbinaryrpc;

class Server::Private {
public:
  Private(QTcpServer *server) : server(server), session_id_iterator(0) {}
  ~Private() {
    if(server) delete server;
  }
  QTcpServer *server;
  int session_id_iterator;
  QMap<int, QTcpSocket *> sessions;
  QMap<int, QMap<int, QVariant::Type>> expected_return_types;
  QList<QHostAddress> allowed_addresses;
  QMap<QString, MethodSignature> method_signatures;
  void sendResponse(QTcpSocket *, QByteArray);
  bool checkMethodParameters(const QString &,
                             const QVariantList &,
                             QVariant::Type *) const;
  bool addRequest(int session_id,
                  int request_id,
                  const QVariant::Type &return_type);
  bool delRequest(int session_id, int request_id);
  bool isExistRequest(int session_id,
                      int request_id,
                      const QVariant::Type &return_type) const;
};

void Server::Private::sendResponse(QTcpSocket *socket, QByteArray data) {
  if(socket == 0) return;
  if(socket->state() == QTcpSocket::ConnectedState ||
     socket->isValid() == true) {
    socket->flush();
    socket->write(data);
  }
}

bool Server::Private::checkMethodParameters(
    const QString &method_name, 
    const QVariantList &parameters, 
    QVariant::Type *return_type) const {
  QList<MethodSignature> signatures = 
      method_signatures.values(method_name);
  foreach(MethodSignature signature, signatures) {
    if (signature.checkParameterTypes(parameters)) {
      if (return_type)
        *return_type = signature.return_type;
      return true;
    }
  }
  return false;
}

bool Server::Private::addRequest(int session_id,
                                 int request_id,
                                 const QVariant::Type &return_type) {
  QMap<int, QMap<int, QVariant::Type>>::iterator i = 
      expected_return_types.find(session_id);
  QMap<int, QVariant::Type> request_id_return_type;
  request_id_return_type.insert(request_id, return_type);
  while (i != expected_return_types.end() && i.key() == session_id) {    
    if(request_id_return_type == i.value())
      return false;
    ++i;
  }
  expected_return_types.insert(session_id, request_id_return_type);
  return true;
}

bool Server::Private::delRequest(int session_id, int request_id) {
  QMap<int, QMap<int, QVariant::Type>>::iterator i = 
      expected_return_types.find(session_id);
  while (i != expected_return_types.end() && i.key() == session_id) {    
    if(i.value().contains(request_id)) {
      expected_return_types.erase(i);
      return true;
    }
    ++i;
  }
  return false;
}

bool Server::Private::isExistRequest(
    int session_id,
    int request_id,
    const QVariant::Type &return_type) const {
  QMap<int, QMap<int, QVariant::Type>>::iterator i = 
    expected_return_types.find(session_id);
  QMap<int, QVariant::Type> request_id_return_type;
  request_id_return_type.insert(request_id, return_type);
  while (i != expected_return_types.end() && i.key() == session_id) {    
    if(request_id_return_type == i.value())
      return true;
    ++i;
  }
  return false;
}

Server::Server(QObject *parent)
    : QObject( parent ),
      d(new Private(new QTcpServer(this))) {
  QObject::connect(d->server, 
                   SIGNAL(newConnection()), 
                   this, 
                   SLOT(newConnection()));
}

Server::~Server() {
  delete d;
}

void Server::setAllowedAddresses(
    const QList<QHostAddress> &allowed_addresses) {
  d->allowed_addresses = allowed_addresses;
}

const QList<QHostAddress> &Server::getAllowedAddresses() const {
  return d->allowed_addresses;
}

QHostAddress Server::getAddress() const {
  return d->server->serverAddress();
}

quint16 Server::getPort() const {
  return d->server->serverPort();
}

bool Server::listen(const QHostAddress & address, quint16 port) {
  return d->server->listen(address, port);
}

bool Server::isListening() const {
  return d->server->isListening();
}

void Server::registerMethod(
    const QString &method_name, 
    QVariant::Type return_type, 
    const QList<QVariant::Type> &parameters_types) {
  d->method_signatures.insert(method_name, 
                              MethodSignature(return_type,
                                              parameters_types));
}

void Server::newConnection() {
  while (d->server->hasPendingConnections()) {
    QTcpSocket *socket(d->server->nextPendingConnection());   
    if (socket == false) return;
    QObject::connect(socket,
                     SIGNAL(disconnected()),
                     socket,
                     SLOT(deleteLater()));
    QHostAddress connected_adddress(socket->peerAddress());
    emit connect(connected_adddress);
    if(d->allowed_addresses.contains(connected_adddress) == false) {
      emit disconnect(connected_adddress);
      return socket->disconnectFromHost();
    }
    d->sessions.insert(d->session_id_iterator, socket);
    new IncomingConnection(this, socket);
    emit createSession(d->session_id_iterator++);
  }
}

void Server::processRequest(const QByteArray &data, QTcpSocket *socket) {
  if(d->sessions.keys(socket).isEmpty()) return;
  Request request;
  int now_position(0), session_id(d->sessions.key(socket));
  if (request.setContent(data, now_position) == false) {
    Response response(-1, 1, "Parsing error RPC request");
    return d->sendResponse(socket, response.composeResponse());
  }
  QString method_name = request.getMethodName();
  QVariantList parameters = request.getParameters();
  if (d->method_signatures.contains(method_name)) {
    QVariant::Type return_type;
    if (d->checkMethodParameters(method_name,
                                 parameters,
                                 &return_type) == false) {
      Response response(request.getId(), 3, "Invalid method parameters");
      return d->sendResponse(socket, response.composeResponse());
    }
    if(d->addRequest(session_id, request.getId(), return_type) == false) {
      Response response(-1, 5, "A similar request already exists");
      return d->sendResponse(socket, response.composeResponse());
    }
    emit incomingRequest(session_id, 
                         request.getId(),
                         method_name,
                         parameters);
  } else {
    Response response(request.getId(), 4, "Method not found");
    d->sendResponse(socket, response.composeResponse());
  }
}

void Server::disconnectSocket(QTcpSocket *socket) {
  emit disconnect(socket->peerAddress());
  int session_id(d->sessions.key(socket));
  d->sessions.remove(session_id);
  emit destroySession(session_id);
}

void Server::sendReturnValue(int session_id,
                             int request_id,
                             const QVariant &return_value) {
  if(d->sessions.contains(session_id) == false) return;
  QTcpSocket *socket(d->sessions.value(session_id));
  if(d->isExistRequest(session_id,
                       request_id,
                       return_value.type()) == false) return;
  d->delRequest(session_id, request_id);
  Response response(request_id,return_value);  
  d->sendResponse(socket, response.composeResponse()); 
}

void Server::sendFault(int session_id,
                       int request_id, 
                       int fault_code, 
                       const QString &fault_string) {
  if(d->sessions.contains(session_id) == false) return;
  QTcpSocket *socket(d->sessions.value(session_id));
  if(d->delRequest(session_id, request_id) == false) return;
  Response response(request_id, fault_code, fault_string);
  d->sendResponse(socket, response.composeResponse());
}

IncomingConnection::IncomingConnection(Server *server, 
                                       QTcpSocket *socket)
    : QObject(server),
      server(server),
      socket(socket) {
  connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
  connect(socket, SIGNAL(disconnected()), this, SLOT(disconnect()));
}

void IncomingConnection::readData() {
  server->processRequest(socket->readAll(), socket);
}

void IncomingConnection::disconnect() {
  server->disconnectSocket(socket);
  this->deleteLater();
}